//! Parse the output of rustc's `.d` dep-info file.
//!
//! Used by the hot-reloading engine and other libraries to provide higher quality dependency analysis
//! for the user's project.

use std::path::{Path, PathBuf};

#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum DepInfoParseError {
    /// The input was malformed - maybe this `.d` format is no longer supported?
    #[error("Malformed input")]
    MalformedInput,

    /// An env var could not be escaped or parsed - this might be a bug in rustc.
    #[error("Failed to parse env var name")]
    InvalidEnvVarName,
}

#[non_exhaustive]
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct RustcDepInfo {
    /// The list of files that the main target in the dep-info file depends on.
    pub files: Vec<PathBuf>,

    /// The list of environment variables we found that the rustc compilation
    /// depends on.
    ///
    /// The first element of the pair is the name of the env var and the second
    /// item is the value. `Some` means that the env var was set, and `None`
    /// means that the env var wasn't actually set and the compilation depends
    /// on it not being set.
    pub env: Vec<(String, Option<String>)>,
}

impl std::str::FromStr for RustcDepInfo {
    type Err = DepInfoParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::new(s)
    }
}

impl RustcDepInfo {
    pub fn from_file(path: &Path) -> Result<RustcDepInfo, DepInfoParseError> {
        let contents =
            std::fs::read_to_string(path).map_err(|_| DepInfoParseError::MalformedInput)?;
        RustcDepInfo::new(&contents)
    }

    /// Parse the `.d` dep-info file generated by rustc.
    pub fn new(contents: &str) -> Result<RustcDepInfo, DepInfoParseError> {
        let mut ret = RustcDepInfo::default();
        let mut found_deps = false;

        for line in contents.lines() {
            if let Some(rest) = line.strip_prefix("# env-dep:") {
                let mut parts = rest.splitn(2, '=');
                let env_var = match parts.next() {
                    Some(s) => s,
                    None => continue,
                };
                let env_val = match parts.next() {
                    Some(s) => Some(unescape_env(s)?),
                    None => None,
                };
                ret.env.push((unescape_env(env_var)?, env_val));
            } else if let Some(pos) = line.find(": ") {
                if found_deps {
                    continue;
                }
                found_deps = true;
                let mut deps = line[pos + 2..].split_whitespace();

                while let Some(s) = deps.next() {
                    let mut file = s.to_string();
                    while file.ends_with('\\') {
                        file.pop();
                        file.push(' ');
                        file.push_str(deps.next().ok_or(DepInfoParseError::MalformedInput)?);
                    }
                    ret.files.push(file.into());
                }
            }
        }
        return Ok(ret);

        // rustc tries to fit env var names and values all on a single line, which
        // means it needs to escape `\r` and `\n`. The escape syntax used is "\n"
        // which means that `\` also needs to be escaped.
        fn unescape_env(s: &str) -> Result<String, DepInfoParseError> {
            let mut ret = String::with_capacity(s.len());
            let mut chars = s.chars();
            while let Some(c) = chars.next() {
                if c != '\\' {
                    ret.push(c);
                    continue;
                }
                match chars.next() {
                    Some('\\') => ret.push('\\'),
                    Some('n') => ret.push('\n'),
                    Some('r') => ret.push('\r'),
                    Some(_) => return Err(DepInfoParseError::InvalidEnvVarName),
                    None => return Err(DepInfoParseError::InvalidEnvVarName),
                }
            }
            Ok(ret)
        }
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn parses_from_path() {
        let contents = include_str!("./dx.d");
        let info: RustcDepInfo = contents.parse().unwrap();
        let answer = vec![
            "/dioxus/packages/autofmt/README.md",
            "/dioxus/packages/autofmt/src/buffer.rs",
            "/dioxus/packages/autofmt/src/collect_macros.rs",
            "/dioxus/packages/autofmt/src/indent.rs",
            "/dioxus/packages/autofmt/src/lib.rs",
            "/dioxus/packages/autofmt/src/prettier_please.rs",
            "/dioxus/packages/autofmt/src/writer.rs",
            "/dioxus/packages/check/README.md",
            "/dioxus/packages/check/src/check.rs",
            "/dioxus/packages/check/src/issues.rs",
            "/dioxus/packages/check/src/lib.rs",
            "/dioxus/packages/check/src/metadata.rs",
            "/dioxus/packages/cli/README.md",
            "/dioxus/packages/cli/assets/android/MainActivity.kt.hbs",
            "/dioxus/packages/cli/assets/android/gen/app/build.gradle.kts.hbs",
            "/dioxus/packages/cli/assets/android/gen/app/proguard-rules.pro",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-hdpi/ic_launcher.webp",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-mdpi/ic_launcher.webp",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xhdpi/ic_launcher.webp",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/colors.xml",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs",
            "/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/styles.xml",
            "/dioxus/packages/cli/assets/android/gen/build.gradle.kts",
            "/dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.jar",
            "/dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.properties",
            "/dioxus/packages/cli/assets/android/gen/gradle.properties",
            "/dioxus/packages/cli/assets/android/gen/gradlew",
            "/dioxus/packages/cli/assets/android/gen/gradlew.bat",
            "/dioxus/packages/cli/assets/android/gen/settings.gradle",
            "/dioxus/packages/cli/assets/dioxus.toml",
            "/dioxus/packages/cli/assets/ios/ios.plist.hbs",
            "/dioxus/packages/cli/assets/macos/mac.plist.hbs",
            "/dioxus/packages/cli/assets/web/index.html",
            "/dioxus/packages/cli/assets/web/loading.html",
            "/dioxus/packages/cli/assets/web/toast.html",
            "/dioxus/packages/cli/build.rs",
            "/dioxus/packages/cli/src/build/builder.rs",
            "/dioxus/packages/cli/src/build/bundle.rs",
            "/dioxus/packages/cli/src/build/mod.rs",
            "/dioxus/packages/cli/src/build/prerender.rs",
            "/dioxus/packages/cli/src/build/progress.rs",
            "/dioxus/packages/cli/src/build/request.rs",
            "/dioxus/packages/cli/src/build/templates.rs",
            "/dioxus/packages/cli/src/build/verify.rs",
            "/dioxus/packages/cli/src/build/web.rs",
            "/dioxus/packages/cli/src/bundle_utils.rs",
            "/dioxus/packages/cli/src/cli/autoformat.rs",
            "/dioxus/packages/cli/src/cli/build.rs",
            "/dioxus/packages/cli/src/cli/bundle.rs",
            "/dioxus/packages/cli/src/cli/check.rs",
            "/dioxus/packages/cli/src/cli/clean.rs",
            "/dioxus/packages/cli/src/cli/config.rs",
            "/dioxus/packages/cli/src/cli/create.rs",
            "/dioxus/packages/cli/src/cli/init.rs",
            "/dioxus/packages/cli/src/cli/link.rs",
            "/dioxus/packages/cli/src/cli/mod.rs",
            "/dioxus/packages/cli/src/cli/run.rs",
            "/dioxus/packages/cli/src/cli/serve.rs",
            "/dioxus/packages/cli/src/cli/target.rs",
            "/dioxus/packages/cli/src/cli/translate.rs",
            "/dioxus/packages/cli/src/cli/verbosity.rs",
            "/dioxus/packages/cli/src/config/app.rs",
            "/dioxus/packages/cli/src/config/bundle.rs",
            "/dioxus/packages/cli/src/config/desktop.rs",
            "/dioxus/packages/cli/src/config/dioxus_config.rs",
            "/dioxus/packages/cli/src/config/serve.rs",
            "/dioxus/packages/cli/src/config/web.rs",
            "/dioxus/packages/cli/src/config.rs",
            "/dioxus/packages/cli/src/dioxus_crate.rs",
            "/dioxus/packages/cli/src/dx_build_info.rs",
            "/dioxus/packages/cli/src/error.rs",
            "/dioxus/packages/cli/src/fastfs.rs",
            "/dioxus/packages/cli/src/filemap.rs",
            "/dioxus/packages/cli/src/logging.rs",
            "/dioxus/packages/cli/src/main.rs",
            "/dioxus/packages/cli/src/metadata.rs",
            "/dioxus/packages/cli/src/platform.rs",
            "/dioxus/packages/cli/src/rustc.rs",
            "/dioxus/packages/cli/src/serve/ansi_buffer.rs",
            "/dioxus/packages/cli/src/serve/detect.rs",
            "/dioxus/packages/cli/src/serve/handle.rs",
            "/dioxus/packages/cli/src/serve/mod.rs",
            "/dioxus/packages/cli/src/serve/output.rs",
            "/dioxus/packages/cli/src/serve/proxy.rs",
            "/dioxus/packages/cli/src/serve/runner.rs",
            "/dioxus/packages/cli/src/serve/server.rs",
            "/dioxus/packages/cli/src/serve/update.rs",
            "/dioxus/packages/cli/src/serve/watcher.rs",
            "/dioxus/packages/cli/src/settings.rs",
            "/dioxus/packages/cli/src/wasm_bindgen.rs",
            "/dioxus/packages/cli-config/src/lib.rs",
            "/dioxus/packages/cli-opt/src/css.rs",
            "/dioxus/packages/cli-opt/src/file.rs",
            "/dioxus/packages/cli-opt/src/folder.rs",
            "/dioxus/packages/cli-opt/src/image/jpg.rs",
            "/dioxus/packages/cli-opt/src/image/mod.rs",
            "/dioxus/packages/cli-opt/src/image/png.rs",
            "/dioxus/packages/cli-opt/src/js.rs",
            "/dioxus/packages/cli-opt/src/json.rs",
            "/dioxus/packages/cli-opt/src/lib.rs",
            "/dioxus/packages/config-macro/README.md",
            "/dioxus/packages/config-macro/src/lib.rs",
            "/dioxus/packages/const-serialize/README.md",
            "/dioxus/packages/const-serialize/src/const_buffers.rs",
            "/dioxus/packages/const-serialize/src/const_vec.rs",
            "/dioxus/packages/const-serialize/src/lib.rs",
            "/dioxus/packages/const-serialize-macro/src/lib.rs",
            "/dioxus/packages/core/README.md",
            "/dioxus/packages/core/docs/common_spawn_errors.md",
            "/dioxus/packages/core/docs/reactivity.md",
            "/dioxus/packages/core/src/any_props.rs",
            "/dioxus/packages/core/src/arena.rs",
            "/dioxus/packages/core/src/diff/component.rs",
            "/dioxus/packages/core/src/diff/iterator.rs",
            "/dioxus/packages/core/src/diff/mod.rs",
            "/dioxus/packages/core/src/diff/node.rs",
            "/dioxus/packages/core/src/effect.rs",
            "/dioxus/packages/core/src/error_boundary.rs",
            "/dioxus/packages/core/src/events.rs",
            "/dioxus/packages/core/src/fragment.rs",
            "/dioxus/packages/core/src/generational_box.rs",
            "/dioxus/packages/core/src/global_context.rs",
            "/dioxus/packages/core/src/hotreload_utils.rs",
            "/dioxus/packages/core/src/launch.rs",
            "/dioxus/packages/core/src/lib.rs",
            "/dioxus/packages/core/src/mutations.rs",
            "/dioxus/packages/core/src/nodes.rs",
            "/dioxus/packages/core/src/properties.rs",
            "/dioxus/packages/core/src/reactive_context.rs",
            "/dioxus/packages/core/src/render_error.rs",
            "/dioxus/packages/core/src/root_wrapper.rs",
            "/dioxus/packages/core/src/runtime.rs",
            "/dioxus/packages/core/src/scheduler.rs",
            "/dioxus/packages/core/src/scope_arena.rs",
            "/dioxus/packages/core/src/scope_context.rs",
            "/dioxus/packages/core/src/scopes.rs",
            "/dioxus/packages/core/src/suspense/component.rs",
            "/dioxus/packages/core/src/suspense/mod.rs",
            "/dioxus/packages/core/src/tasks.rs",
            "/dioxus/packages/core/src/virtual_dom.rs",
            "/dioxus/packages/core-macro/README.md",
            "/dioxus/packages/core-macro/docs/component.md",
            "/dioxus/packages/core-macro/docs/props.md",
            "/dioxus/packages/core-macro/docs/rsx.md",
            "/dioxus/packages/core-macro/src/component.rs",
            "/dioxus/packages/core-macro/src/lib.rs",
            "/dioxus/packages/core-macro/src/props/mod.rs",
            "/dioxus/packages/core-macro/src/utils.rs",
            "/dioxus/packages/core-types/src/bubbles.rs",
            "/dioxus/packages/core-types/src/bundled.rs",
            "/dioxus/packages/core-types/src/formatter.rs",
            "/dioxus/packages/core-types/src/hr_context.rs",
            "/dioxus/packages/core-types/src/lib.rs",
            "/dioxus/packages/devtools/src/lib.rs",
            "/dioxus/packages/devtools-types/src/lib.rs",
            "/dioxus/packages/dioxus-lib/README.md",
            "/dioxus/packages/dioxus-lib/src/lib.rs",
            "/dioxus/packages/document/build.rs",
            "/dioxus/packages/document/docs/eval.md",
            "/dioxus/packages/document/docs/head.md",
            "/dioxus/packages/document/src/document.rs",
            "/dioxus/packages/document/src/elements/link.rs",
            "/dioxus/packages/document/src/elements/meta.rs",
            "/dioxus/packages/document/src/elements/mod.rs",
            "/dioxus/packages/document/src/elements/script.rs",
            "/dioxus/packages/document/src/elements/style.rs",
            "/dioxus/packages/document/src/elements/stylesheet.rs",
            "/dioxus/packages/document/src/elements/title.rs",
            "/dioxus/packages/document/src/error.rs",
            "/dioxus/packages/document/src/eval.rs",
            "/dioxus/packages/document/src/js/head.js",
            "/dioxus/packages/document/src/lib.rs",
            "/dioxus/packages/document/./src/ts/eval.ts",
            "/dioxus/packages/document/./src/ts/head.ts",
            "/dioxus/packages/dx-wire-format/src/lib.rs",
            "/dioxus/packages/fullstack/README.md",
            "/dioxus/packages/fullstack/src/document/mod.rs",
            "/dioxus/packages/fullstack/src/hooks/mod.rs",
            "/dioxus/packages/fullstack/src/hooks/server_cached.rs",
            "/dioxus/packages/fullstack/src/hooks/server_future.rs",
            "/dioxus/packages/fullstack/src/html_storage/mod.rs",
            "/dioxus/packages/fullstack/src/lib.rs",
            "/dioxus/packages/generational-box/README.md",
            "/dioxus/packages/generational-box/src/entry.rs",
            "/dioxus/packages/generational-box/src/error.rs",
            "/dioxus/packages/generational-box/src/lib.rs",
            "/dioxus/packages/generational-box/src/references.rs",
            "/dioxus/packages/generational-box/src/sync.rs",
            "/dioxus/packages/generational-box/src/unsync.rs",
            "/dioxus/packages/history/src/lib.rs",
            "/dioxus/packages/history/src/memory.rs",
            "/dioxus/packages/hooks/README.md",
            "/dioxus/packages/hooks/docs/derived_state.md",
            "/dioxus/packages/hooks/docs/moving_state_around.md",
            "/dioxus/packages/hooks/docs/rules_of_hooks.md",
            "/dioxus/packages/hooks/docs/side_effects.md",
            "/dioxus/packages/hooks/docs/use_resource.md",
            "/dioxus/packages/hooks/src/lib.rs",
            "/dioxus/packages/hooks/src/use_callback.rs",
            "/dioxus/packages/hooks/src/use_context.rs",
            "/dioxus/packages/hooks/src/use_coroutine.rs",
            "/dioxus/packages/hooks/src/use_effect.rs",
            "/dioxus/packages/hooks/src/use_future.rs",
            "/dioxus/packages/hooks/src/use_hook_did_run.rs",
            "/dioxus/packages/hooks/src/use_memo.rs",
            "/dioxus/packages/hooks/src/use_on_destroy.rs",
            "/dioxus/packages/hooks/src/use_reactive.rs",
            "/dioxus/packages/hooks/src/use_resource.rs",
            "/dioxus/packages/hooks/src/use_root_context.rs",
            "/dioxus/packages/hooks/src/use_set_compare.rs",
            "/dioxus/packages/hooks/src/use_signal.rs",
            "/dioxus/packages/html/README.md",
            "/dioxus/packages/html/docs/common_event_handler_errors.md",
            "/dioxus/packages/html/docs/event_handlers.md",
            "/dioxus/packages/html/src/attribute_groups.rs",
            "/dioxus/packages/html/src/elements.rs",
            "/dioxus/packages/html/src/events/animation.rs",
            "/dioxus/packages/html/src/events/clipboard.rs",
            "/dioxus/packages/html/src/events/composition.rs",
            "/dioxus/packages/html/src/events/drag.rs",
            "/dioxus/packages/html/src/events/focus.rs",
            "/dioxus/packages/html/src/events/form.rs",
            "/dioxus/packages/html/src/events/image.rs",
            "/dioxus/packages/html/src/events/keyboard.rs",
            "/dioxus/packages/html/src/events/media.rs",
            "/dioxus/packages/html/src/events/mod.rs",
            "/dioxus/packages/html/src/events/mounted.rs",
            "/dioxus/packages/html/src/events/mouse.rs",
            "/dioxus/packages/html/src/events/pointer.rs",
            "/dioxus/packages/html/src/events/resize.rs",
            "/dioxus/packages/html/src/events/scroll.rs",
            "/dioxus/packages/html/src/events/selection.rs",
            "/dioxus/packages/html/src/events/toggle.rs",
            "/dioxus/packages/html/src/events/touch.rs",
            "/dioxus/packages/html/src/events/transition.rs",
            "/dioxus/packages/html/src/events/visible.rs",
            "/dioxus/packages/html/src/events/wheel.rs",
            "/dioxus/packages/html/src/file_data.rs",
            "/dioxus/packages/html/src/geometry.rs",
            "/dioxus/packages/html/src/input_data.rs",
            "/dioxus/packages/html/src/lib.rs",
            "/dioxus/packages/html/src/point_interaction.rs",
            "/dioxus/packages/html/src/render_template.rs",
            "/dioxus/packages/html-internal-macro/src/lib.rs",
            "/dioxus/packages/lazy-js-bundle/src/lib.rs",
            "/dioxus/packages/manganis/manganis/README.md",
            "/dioxus/packages/manganis/manganis/src/hash.rs",
            "/dioxus/packages/manganis/manganis/src/lib.rs",
            "/dioxus/packages/manganis/manganis/src/macro_helpers.rs",
            "/dioxus/packages/manganis/manganis-core/src/asset.rs",
            "/dioxus/packages/manganis/manganis-core/src/css.rs",
            "/dioxus/packages/manganis/manganis-core/src/folder.rs",
            "/dioxus/packages/manganis/manganis-core/src/hash.rs",
            "/dioxus/packages/manganis/manganis-core/src/images.rs",
            "/dioxus/packages/manganis/manganis-core/src/js.rs",
            "/dioxus/packages/manganis/manganis-core/src/lib.rs",
            "/dioxus/packages/manganis/manganis-core/src/linker.rs",
            "/dioxus/packages/manganis/manganis-core/src/options.rs",
            "/dioxus/packages/manganis/manganis-macro/README.md",
            "/dioxus/packages/manganis/manganis-macro/src/asset.rs",
            "/dioxus/packages/manganis/manganis-macro/src/lib.rs",
            "/dioxus/packages/manganis/manganis-macro/src/linker.rs",
            "/dioxus/packages/rsx/src/assign_dyn_ids.rs",
            "/dioxus/packages/rsx/src/attribute.rs",
            "/dioxus/packages/rsx/src/component.rs",
            "/dioxus/packages/rsx/src/diagnostics.rs",
            "/dioxus/packages/rsx/src/element.rs",
            "/dioxus/packages/rsx/src/expr_node.rs",
            "/dioxus/packages/rsx/src/forloop.rs",
            "/dioxus/packages/rsx/src/ifchain.rs",
            "/dioxus/packages/rsx/src/ifmt.rs",
            "/dioxus/packages/rsx/src/lib.rs",
            "/dioxus/packages/rsx/src/literal.rs",
            "/dioxus/packages/rsx/src/location.rs",
            "/dioxus/packages/rsx/src/node.rs",
            "/dioxus/packages/rsx/src/partial_closure.rs",
            "/dioxus/packages/rsx/src/raw_expr.rs",
            "/dioxus/packages/rsx/src/rsx_block.rs",
            "/dioxus/packages/rsx/src/rsx_call.rs",
            "/dioxus/packages/rsx/src/template_body.rs",
            "/dioxus/packages/rsx/src/text_node.rs",
            "/dioxus/packages/rsx/src/util.rs",
            "/dioxus/packages/rsx-hotreload/src/collect.rs",
            "/dioxus/packages/rsx-hotreload/src/diff.rs",
            "/dioxus/packages/rsx-hotreload/src/extensions.rs",
            "/dioxus/packages/rsx-hotreload/src/last_build_state.rs",
            "/dioxus/packages/rsx-hotreload/src/lib.rs",
            "/dioxus/packages/rsx-rosetta/README.md",
            "/dioxus/packages/rsx-rosetta/src/lib.rs",
            "/dioxus/packages/server-macro/src/lib.rs",
            "/dioxus/packages/signals/README.md",
            "/dioxus/packages/signals/docs/hoist/error.rs",
            "/dioxus/packages/signals/docs/hoist/fixed_list.rs",
            "/dioxus/packages/signals/docs/memo.md",
            "/dioxus/packages/signals/docs/signals.md",
            "/dioxus/packages/signals/src/copy_value.rs",
            "/dioxus/packages/signals/src/global/memo.rs",
            "/dioxus/packages/signals/src/global/mod.rs",
            "/dioxus/packages/signals/src/global/signal.rs",
            "/dioxus/packages/signals/src/impls.rs",
            "/dioxus/packages/signals/src/lib.rs",
            "/dioxus/packages/signals/src/map.rs",
            "/dioxus/packages/signals/src/memo.rs",
            "/dioxus/packages/signals/src/props.rs",
            "/dioxus/packages/signals/src/read.rs",
            "/dioxus/packages/signals/src/set_compare.rs",
            "/dioxus/packages/signals/src/signal.rs",
            "/dioxus/packages/signals/src/warnings.rs",
            "/dioxus/packages/signals/src/write.rs",
            "/dioxus/target/debug/build/dioxus-cli-90993e55e02b7cee/out/built.rs",
        ];
        assert_eq!(
            answer.iter().map(PathBuf::from).collect::<Vec<_>>(),
            info.files
        );
    }
}
